﻿/*----------------------------------------------------------------
//  <copyright file="DingTalkHandler.cs" company="MicroCloud@151504200868">
//      Copyright © 2020-2024 MicroCloud Corporation, All rights reserved.
//  </copyright>
//  <site>https://gitee.com/chenmm123/microclouds</site>
//  <last-editor>cmm</last-editor>
//  <last-date>2023-12-06 08:32</last-date>
//----------------------------------------------------------------*/

namespace MicroCloud.Authentication.OAuth2.DingTalk
{
    #region "钉钉认证处理器"
    /// <summary>
    /// 钉钉认证处理器
    /// </summary>
    /// <remarks>
    /// 初始化一个钉钉认证处理器 <see cref="DingTalkHandler"/> 的新实例
    /// </remarks>
    /// <param name="options"></param>
    /// <param name="logger"></param>
    /// <param name="encoder"></param>
    internal class DingTalkHandler(IOptionsMonitor<DingTalkOptions> options, ILoggerFactory logger, UrlEncoder encoder) : OAuthHandler<DingTalkOptions>(options, logger, encoder)
    {
        #region "受保护的重写方法 - Overrides of OAuthHandler<DingTalkOptions>"
        #region "Step 1：请求CODE，构建用户授权地址"
        /// <summary>
        /// Step 1：请求CODE，构建用户授权地址
        /// </summary>
        /// <param name="properties"></param>
        /// <param name="redirectUri"></param>
        /// <returns></returns>
        protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
        {
            return QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, new Dictionary<string, string>
            {
                ["appid"] = Options.ClientId,
                ["redirect_uri"] = redirectUri,
                ["response_type"] = "code",
                ["scope"] = FormatScope(),
                ["state"] = Options.StateDataFormat.Protect(properties)
            });
        }
        #endregion
        #region "Step 2：通过code获取access_token"
        /// <summary>
        /// Step 2：通过code获取access_token
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        protected override async Task<OAuthTokenResponse> ExchangeCodeAsync(OAuthCodeExchangeContext context)
        {
            string code = context.Code;
            // string redirectUri = context.RedirectUri;

            //钉钉外部应用获取用户信息不依据token而是依据code，所以这里直接用code封装返回
            var responseContentJson = $"{{\"access_token\":\"{code}\"}}";

            JsonDocument document = JsonDocument.Parse(responseContentJson);

            var response = OAuthTokenResponse.Success(document);
            return await Task.Factory.StartNew(() => response);
        }
        #endregion
        #region "Step 3：从远程服务器创建票证"
        /// <summary>
        /// Step 3：从远程服务器创建票证
        /// </summary>
        /// <param name="identity"></param>
        /// <param name="properties"></param>
        /// <param name="tokens"></param>
        /// <returns></returns>
        protected override async Task<AuthenticationTicket> CreateTicketAsync(
            ClaimsIdentity identity,
            AuthenticationProperties properties,
            OAuthTokenResponse tokens)
        {
            var tmpAuthCode = tokens.AccessToken;
            var timestamp = GetTimeStamp();
            // 根据timestamp, appSecret计算签名值
            var signature = Hash_hmac2(timestamp.ToString(), Options.AppKey);

            var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, new Dictionary<string, string>
            {
                ["accessKey"] = Options.AppId,
                ["timestamp"] = timestamp.ToString(),
                ["signature"] = signature
            });

            var bodyData = $"{{\"tmp_auth_code\":\"{tmpAuthCode}\"}}";
            var response = await Backchannel.PostAsync(address, new StringContent(bodyData));

            string responseContentJson = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("检索用户配置文件时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);

                throw new HttpRequestException("检索用户信息时出错");
            }

            var payload = JObject.Parse(responseContentJson);
            JsonDocument document = JsonDocument.Parse(responseContentJson);
            if (payload.Value<string>("errcode") != "0")
            {
                Logger.LogError("检索用户配置文件时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);

                throw new HttpRequestException("检索用户信息时出错");
            }

            var userInfo = payload.Value<JObject>("user_info");
            var unionId = userInfo.Value<string>("unionid");
            var openId = userInfo.Value<string>("openid");
            var nickName = userInfo.Value<string>("nick");

            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, unionId, Options.ClaimsIssuer));
            identity.AddClaim(new Claim(ClaimTypes.Name, nickName, Options.ClaimsIssuer));
            identity.AddClaim(new Claim(ClaimTypeConstants.OpenIdEx, openId, Options.ClaimsIssuer));
            // identity.AddClaim(new Claim(ClaimTypeConstants.HeadImgEx, "", Options.ClaimsIssuer));

            var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, document.RootElement);
            context.RunClaimActions();

            await Events.CreatingTicket(context);

            return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
        }
        #endregion

        #endregion

        #region "私有方法"
        #region "格式化作用域"
        /// <summary>
        /// 格式化作用域
        /// </summary>
        /// <returns></returns>
        protected override string FormatScope()
        {
            return string.Join(",", Options.Scope);
        }
        #endregion
        #region "获取时间戳"
        /// <summary>
        /// 获取时间戳
        /// </summary>
        /// <returns></returns>
        private static long GetTimeStamp()
        {
            //TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            //var timestamp = Convert.ToInt64(ts.TotalMilliseconds).ToString();

            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalMilliseconds);
        }
        #endregion
        #region "签名算法"
        /// <summary>
        /// 签名算法
        /// </summary>
        /// <param name="message"></param>
        /// <param name="secret"></param>
        /// <returns></returns>
        private static string Hash_hmac2(string message, string secret)
        {
            secret ??= "";
            var encoding = new ASCIIEncoding();
            byte[] keyByte = encoding.GetBytes(secret);
            byte[] messageBytes = encoding.GetBytes(message);
            using var hmacsha256 = new HMACSHA256(keyByte);
            byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
            return Convert.ToBase64String(hashmessage);
        }
        #endregion

        #endregion

    }
    #endregion

}
